"""
Tests for authentication rate limiting (login and registration endpoints).
Tests that brute force protection is working correctly.
"""

import pytest


class TestAuthRateLimiting:
    """Test rate limiting on authentication endpoints."""

    @pytest.fixture
    def app(self):
        """Create a test Flask app with rate limiting."""
        from src.local_deep_research.web.app_factory import create_app

        app, _ = create_app()
        app.config["TESTING"] = True
        app.config["WTF_CSRF_ENABLED"] = False  # Disable CSRF for testing
        return app

    @pytest.fixture
    def client(self, app):
        """Create a test client."""
        return app.test_client()

    def test_login_rate_limit_allows_5_attempts(self, client):
        """Test that login allows 5 attempts before rate limiting."""
        # Make 5 login attempts - should all be allowed
        for i in range(5):
            response = client.post(
                "/auth/login",
                data={"username": f"testuser{i}", "password": "wrongpassword"},
                follow_redirects=False,
            )
            # Should get 401 (invalid credentials) not 429 (rate limit)
            assert response.status_code in [
                200,
                401,
                400,
            ], f"Attempt {i + 1} should not be rate limited"

    def test_login_rate_limit_blocks_6th_attempt(self, client):
        """Test that login blocks the 6th attempt within 15 minutes."""
        # Make 5 login attempts
        for i in range(5):
            client.post(
                "/auth/login",
                data={"username": f"testuser{i}", "password": "wrongpassword"},
            )

        # 6th attempt should be rate limited
        response = client.post(
            "/auth/login",
            data={"username": "testuser6", "password": "wrongpassword"},
        )
        assert response.status_code == 429, "6th attempt should be rate limited"

    def test_login_rate_limit_returns_proper_error(self, client):
        """Test that rate limit returns proper JSON error response."""
        # Trigger rate limit
        for i in range(6):
            response = client.post(
                "/auth/login",
                data={"username": f"testuser{i}", "password": "wrongpassword"},
            )

        # Check error response format
        if response.status_code == 429:
            data = response.get_json()
            assert "error" in data
            assert "message" in data
            assert "Too many" in data["message"] or "Too many" in data["error"]

    def test_login_rate_limit_includes_retry_after_header(self, client):
        """Test that 429 response includes Retry-After header."""
        # Trigger rate limit
        for i in range(6):
            response = client.post(
                "/auth/login",
                data={"username": f"testuser{i}", "password": "wrongpassword"},
            )

        # Check for Retry-After header
        if response.status_code == 429:
            assert (
                "Retry-After" in response.headers
                or "X-RateLimit-Reset" in response.headers
            ), "Rate limit response should include retry timing header"

    def test_registration_rate_limit_allows_3_attempts(self, client):
        """Test that registration allows 3 attempts before rate limiting."""
        # Make 3 registration attempts - should all be allowed
        for i in range(3):
            response = client.post(
                "/auth/register",
                data={
                    "username": f"newuser{i}",
                    "password": "testpass123",
                    "confirm_password": "testpass123",
                    "acknowledge": "true",
                },
                follow_redirects=False,
            )
            # Should get 200/302 (success/redirect) or 400 (validation error)
            # but not 429 (rate limit)
            assert response.status_code in [
                200,
                302,
                400,
            ], f"Attempt {i + 1} should not be rate limited"

    def test_registration_rate_limit_blocks_4th_attempt(self, client):
        """Test that registration blocks the 4th attempt within 1 hour."""
        # Make 3 registration attempts
        for i in range(3):
            client.post(
                "/auth/register",
                data={
                    "username": f"newuser{i}",
                    "password": "testpass123",
                    "confirm_password": "testpass123",
                    "acknowledge": "true",
                },
            )

        # 4th attempt should be rate limited
        response = client.post(
            "/auth/register",
            data={
                "username": "newuser4",
                "password": "testpass123",
                "confirm_password": "testpass123",
                "acknowledge": "true",
            },
        )
        assert response.status_code == 429, "4th attempt should be rate limited"

    def test_different_endpoints_have_separate_limits(self, client):
        """Test that login and registration have independent rate limits."""
        # Exhaust login limit (5 attempts)
        for i in range(5):
            client.post(
                "/auth/login",
                data={"username": f"testuser{i}", "password": "wrongpassword"},
            )

        # Registration should still work (separate limit)
        response = client.post(
            "/auth/register",
            data={
                "username": "newuser1",
                "password": "testpass123",
                "confirm_password": "testpass123",
                "acknowledge": "true",
            },
        )
        assert response.status_code != 429, (
            "Registration should have separate rate limit from login"
        )

    def test_rate_limit_is_per_ip(self, client, app):
        """Test that rate limiting is applied per IP address."""
        # Make 5 requests from "IP 1"
        for i in range(5):
            with app.test_request_context(
                "/auth/login",
                method="POST",
                environ_base={"REMOTE_ADDR": "192.168.1.1"},
            ):
                client.post(
                    "/auth/login",
                    data={
                        "username": f"testuser{i}",
                        "password": "wrongpassword",
                    },
                    environ_base={"REMOTE_ADDR": "192.168.1.1"},
                )

        # 6th request from "IP 1" should be rate limited
        response1 = client.post(
            "/auth/login",
            data={"username": "testuser6", "password": "wrongpassword"},
            environ_base={"REMOTE_ADDR": "192.168.1.1"},
        )

        # Request from "IP 2" should still work (different IP)
        response2 = client.post(
            "/auth/login",
            data={"username": "testuser7", "password": "wrongpassword"},
            environ_base={"REMOTE_ADDR": "192.168.1.2"},
        )

        assert response1.status_code == 429, "IP 1 should be rate limited"
        assert response2.status_code != 429, (
            "IP 2 should not be rate limited (different IP)"
        )

    def test_proxy_headers_are_respected(self, client):
        """Test that X-Forwarded-For headers are used for rate limiting."""
        # Make 5 requests with same X-Forwarded-For header
        for i in range(5):
            client.post(
                "/auth/login",
                data={"username": f"testuser{i}", "password": "wrongpassword"},
                headers={"X-Forwarded-For": "10.0.0.1"},
            )

        # 6th request with same X-Forwarded-For should be rate limited
        response = client.post(
            "/auth/login",
            data={"username": "testuser6", "password": "wrongpassword"},
            headers={"X-Forwarded-For": "10.0.0.1"},
        )

        assert response.status_code == 429, (
            "Requests from same X-Forwarded-For IP should be rate limited"
        )

    def test_successful_login_still_counts_toward_limit(self, client):
        """Test that successful logins also count toward rate limit."""
        # This prevents attackers from resetting the limit with valid credentials
        # Create a test user first (this uses the programmatic API, not the web endpoint)
        from src.local_deep_research.database.encrypted_db import db_manager

        test_username = "ratelimituser"
        test_password = "testpass123"

        # Create user if doesn't exist
        if not db_manager.user_exists(test_username):
            db_manager.create_user_database(test_username, test_password)

        # Make 5 successful login attempts
        for i in range(5):
            client.post(
                "/auth/login",
                data={"username": test_username, "password": test_password},
            )

        # 6th attempt should still be rate limited, even with valid credentials
        response = client.post(
            "/auth/login",
            data={"username": test_username, "password": test_password},
        )

        assert response.status_code == 429, (
            "Even successful logins should count toward rate limit"
        )

        # Clean up: delete test user
        db_manager.close_user_database(test_username)

    def test_account_enumeration_prevented(self, client):
        """Test that registration errors don't reveal username existence."""
        # Try to register with a username that definitely doesn't exist
        response1 = client.post(
            "/auth/register",
            data={
                "username": "definitelynonexistentuser12345",
                "password": "short",  # Will fail validation
                "confirm_password": "short",
                "acknowledge": "true",
            },
        )

        # Try to register with a username that exists (from previous test)
        response2 = client.post(
            "/auth/register",
            data={
                "username": "ratelimituser",  # Exists from previous test
                "password": "testpass123",
                "confirm_password": "testpass123",
                "acknowledge": "true",
            },
        )

        # Both should return generic errors, not revealing if username exists
        if response1.status_code == 400 and response2.status_code == 400:
            # Check that error messages are generic
            data2 = response2.get_data(as_text=True)

            # Should NOT contain "Username already exists"
            assert "Username already exists" not in data2, (
                "Error should not reveal username existence"
            )
            # Should contain generic message
            assert (
                "Registration failed" in data2
                or "try a different username" in data2
            ), "Should use generic error message"


class TestRateLimitReset:
    """Test that rate limits reset after the time window."""

    @pytest.fixture
    def app(self):
        """Create a test Flask app with rate limiting."""
        from src.local_deep_research.web.app_factory import create_app

        app, _ = create_app()
        app.config["TESTING"] = True
        app.config["WTF_CSRF_ENABLED"] = False
        return app

    @pytest.fixture
    def client(self, app):
        """Create a test client."""
        return app.test_client()

    @pytest.mark.slow
    def test_rate_limit_resets_after_time_window(self, client):
        """Test that rate limit resets after 15 minutes (for login)."""
        # Note: This test would take 15 minutes to run in real time
        # In practice, you'd mock time or use a shorter limit for testing
        pytest.skip(
            "This test requires mocking time or waiting 15 minutes - "
            "implement with time mocking if needed"
        )

        # Implementation would look like:
        # 1. Make 5 login attempts
        # 2. Verify 6th attempt is blocked
        # 3. Fast-forward time by 15 minutes (using time mocking)
        # 4. Verify new attempt is allowed
